// VObjectManager.cpp
//	
#include "VObjectManager.h"

#include "OpenGL/Renderer.h"
#include "OpenGL/OpenGL.h"

#include "VObject.h"
#include "SelectionSet.h"

#include <assert.h>

#include <iostream>

#include "MemCheck.h"

// ------------------------------------------------------------------------------------------------
VObjectManager::VObjectManager( OpenGL::Renderer *r )
: _renderer(r),
  _hilightedObject(0),
  _selectionSet( new SelectionSet )
{}

// ------------------------------------------------------------------------------------------------
VObjectManager::~VObjectManager()
{
	destroyAllObjects();

	delete _selectionSet;
}

// ------------------------------------------------------------------------------------------------
bool VObjectManager::addObject( VObject *o )
{
	if( !o )
		return false;

	if( vObjects.find( o->id() ) != vObjects.end() )
		return false;

	vObjects[ o->id() ] = o;
	
	createIndex(o);
	
	return true;
}

// ------------------------------------------------------------------------------------------------
VObject *VObjectManager::removeObject( unsigned int id )
{
	Objects::iterator it = vObjects.find( id );

	if( it == vObjects.end() )
		return 0; // object not found

	VObject *obj = it->second;

	removeIndex( obj );
	_selectionSet->removeObject(id);


	vObjects.erase(it);

	return obj;
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::destroyAllObjects()
{
	for( Objects::iterator it = vObjects.begin();
		 it != vObjects.end();
		 ++it )
	{
		delete it->second;
	}

	_selectionSet->clear();
	vObjects.clear();
	vObjectIndex.clear();
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::updateObjects()
{}

// ------------------------------------------------------------------------------------------------
void VObjectManager::createIndex( VObject *obj )
{
	if( !obj )
		return;

	ObjectIndex::iterator it = vObjectIndex.find( obj->type() );

	if( it == vObjectIndex.end() )
	{
		// create a new indexing list for this type
		vObjectIndex[ obj->type() ].push_back(obj);
	}
	else
	{
		// add this object to the list of the same type
		it->second.push_back( obj );
	}
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::removeIndex( VObject *obj )
{
	if( !obj )
		return;

	ObjectIndex::iterator it = vObjectIndex.find( obj->type() );

	assert( it != vObjectIndex.end() );
	
	it->second.remove(obj);
}


// ------------------------------------------------------------------------------------------------
OpenGL::Math::box3 VObjectManager::boundingBox() const
{
	OpenGL::Math::box3 box;

	for( Objects::const_iterator it = vObjects.begin();
		 it != vObjects.end();
		 ++it )
	{
		box.extend( it->second->boundingBox() );
	}
	
	return box;
}

// ------------------------------------------------------------------------------------------------
VObject *VObjectManager::getObject( unsigned int id )
{
	Objects::iterator it = vObjects.find( id );

	if( it == vObjects.end() )
		return 0; // object not found

	return it->second;
}

// ------------------------------------------------------------------------------------------------
VObject *VObjectManager::getHilightedObject() const
{
	return _hilightedObject;
}


// ------------------------------------------------------------------------------------------------
void VObjectManager::hilightObject( VObject *obj )
{
	if(_hilightedObject == obj)
		return;

	// undo highlighting on the current object
	if(_hilightedObject)	
		_hilightedObject->setHilighted(false);

	// get new highlighted object (it may be NULL)
	_hilightedObject = obj;

	// set highlighting to the hovered object (if any)
	if(_hilightedObject)	
		_hilightedObject->setHilighted(true);
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::hilightObjectAt( const OpenGL::Math::dvec3 &p )
{
	_pickPoint = _renderer->convertWorldToWindow(p,false);
	
	std::vector<unsigned int> v = _renderer->getSelectionNames();

	if( v.empty() )
	{
		hilightObject(0);
		return;
	}

	int vsize = v.size();


	Objects::iterator it = vObjects.find(v[0]);

	hilightObject( it->second );
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::selectArea( const OpenGL::Math::dvec3 &p0, const OpenGL::Math::dvec3 &p1 )
{
	const double threshold = _renderer->pixelToWorldSize(4); // four pixel threshold
	
	if( OpenGL::Math::distance( p0,p1 ) < threshold )
		selectObjectAt(p0);
	else
	{
		_selectionSet->setPolicy( SelectionSet::PolicyAdd );

		OpenGL::Math::box3 selectionArea;
		selectionArea.extend(p0);
		selectionArea.extend(p1);

		for( Objects::iterator it = vObjects.begin();
			 it != vObjects.end();
			 ++it )
		{
			if( selectionArea.overlaps( it->second->boundingBox() ) )
				if( selectionArea.contains( it->second->boundingBox() ) || it->second->intersects( selectionArea ) )
					it->second->setSelected( _selectionSet->addObject(it->first) );
		}		
	}
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::selectObjectAt( const OpenGL::Math::dvec3 &p )
{
	_pickPoint = _renderer->convertWorldToWindow(p,false);
	
	std::vector<unsigned int> v = _renderer->getSelectionNames();

	VObject *obj = 0;
	if( v.empty() )
	{
		// clear selection
		for( Objects::iterator it = vObjects.begin();
			 it != vObjects.end();
			 ++it )
			it->second->setSelected(false);	

		_selectionSet->clear();
	}
	else
	{
		_selectionSet->setPolicy( SelectionSet::PolicyExclusive );

		Objects::iterator it = vObjects.find(v[0]);
		obj = it->second;
		obj->setSelected( _selectionSet->addObject(v[0]) );	
	}


	hilightObject( obj );	
}

// ------------------------------------------------------------------------------------------------
std::vector<unsigned int> VObjectManager::getSelectedObjects() const
{
	std::vector<unsigned int> selectedObjects;

	SelectionSet::SelectionList list = _selectionSet->get();

	for( SelectionSet::SelectionList::iterator it = list.begin();
		 it != list.end();
		 ++it )
		selectedObjects.push_back( *it );

	return selectedObjects;
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::drawObjects()
{
	glPushAttrib( GL_ALL_ATTRIB_BITS );
	{
		glEnable(GL_POINT_SMOOTH);
		glEnable(GL_LINE_SMOOTH);
		glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
		glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

		glDepthMask(GL_FALSE);
		glDisable(GL_DEPTH_TEST);
	
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); 

		const bool textEnabled  = true;
		const int  textSize     = 16;
		OpenGL::Color textColor = OpenGL::Color::white();


		// draw objects by type (shapes, lines, points)
		for( ObjectIndex::reverse_iterator it = vObjectIndex.rbegin();
			 it != vObjectIndex.rend();
			 ++it )
		{
			for( ObjectList::iterator jt = it->second.begin();
				 jt != it->second.end();
				 ++jt )
			{
				if( !(*jt)->isHilighted() )
				{	
					(*jt)->draw(_renderer);

					if( textEnabled && (*jt)->isVisible() )
						_renderer->addText( (*jt)->textPosition(), (*jt)->name(), textColor, textSize, OpenGL::TextRenderer::AlignCenter );
				}
			}
		}

		if( _hilightedObject )
		{
			_hilightedObject->draw(_renderer);
			_renderer->addText( _hilightedObject->textPosition(), _hilightedObject->name(), _hilightedObject->color() , textSize, OpenGL::TextRenderer::AlignCenter );
		}
	}
	glPopAttrib();

	drawForSelection();
}

// ------------------------------------------------------------------------------------------------
void VObjectManager::drawForSelection( )
{
	glPushAttrib(GL_POINT_BIT | GL_LINE_BIT | GL_ENABLE_BIT);
	glDisable(GL_TEXTURE_2D);
	glColor3ub(255,255,255);

	do
	{
		_renderer->openSelectionMode((int)_pickPoint.x,(int)_pickPoint.y);

		const int typesCount  = (int)vObjectIndex.size();
		const float depthStep = 1.f / typesCount;
		float currDepth = 0.f;

		// draw objects by type (shapes, lines, points)
		for( ObjectIndex::reverse_iterator it = vObjectIndex.rbegin();
			 it != vObjectIndex.rend();
			 ++it )
		{
			glPushMatrix();
			glTranslatef(0,0,currDepth);
			for( ObjectList::iterator jt = it->second.begin();
				 jt != it->second.end();
				 ++jt )
			{
				if( (*jt)->isVisible() )
					(*jt)->drawForSelection(_renderer);			
			}
			glPopMatrix();

			currDepth += depthStep;
		}

		_renderer->closeSelectionMode();
	}
	while( _renderer->selectionFailed() );

	glPopAttrib();
}